/**
 * \file test_scalar.cpp
 * 
 * \section COPYRIGHT
 *
 * Basis: A 3D Mathematics Library
 *
 * ---------------------------------------------------------------------
 *
 * Copyright (c) 2010, Don Olmstead
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *  3. Neither the name of organization nor the names of its contributors may be
 *     used to endorse or promote products derived from this software without
 *     specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "basis_test.hpp"

/// The scalar implementation to check
typedef basis::scalar<real_type, simd_type> scalar_type;

/**
 * Contains test values to check the scalar interface.
 */
struct ScalarTestValues
{
	/// A real number type
	typedef scalar_type::value_type value_type;

	/**
	 * How close the values need to be to be considered equal.
	 *
	 * Used to compare floating point values.
	 */
	const value_type tolerance;
	/// A value
	const value_type scalar1;
	/// A value
	const value_type scalar2;
	/// An array containing some values.
	value_type values1[4];

	/**
	 * Initializes the test values.
	 */
	ScalarTestValues()
	: scalar1((value_type)2)
	, scalar2((value_type)4)
	, tolerance((value_type)0.001)
	{
		// Initialize some vector arrays
		values1[0] = (value_type)1;
		values1[1] = (value_type)2;
		values1[2] = (value_type)3;
		values1[3] = (value_type)4;
	}

} ; // end struct ScalarTestValues

SUITE(ScalarInterface)
{

	//------------------------------------------------------------
	// Constructor tests
	//------------------------------------------------------------

	TEST_FIXTURE(ScalarTestValues, Constructor)
	{
		scalar_type test(scalar1);

		CHECK_CLOSE(scalar1, (value_type)test, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, CopyConstructor)
	{
		scalar_type copy(scalar1);
		scalar_type test(copy);

		CHECK_CLOSE(scalar1, (value_type)copy, tolerance);
	}

	//------------------------------------------------------------
	// Comparison operators
	//------------------------------------------------------------

	TEST_FIXTURE(ScalarTestValues, EqualTest)
	{
		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);

		CHECK(lhs == lhs);
		CHECK((lhs == rhs) == (scalar1 == scalar2));
	}

	TEST_FIXTURE(ScalarTestValues, NotEqualTest)
	{
		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);

		CHECK((lhs != lhs) == false);
		CHECK((lhs != rhs) == (scalar1 != scalar2));
	}
	
	TEST_FIXTURE(ScalarTestValues, LessThanTest)
	{
		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);

		CHECK((lhs < rhs) == (scalar1 < scalar2));
	}

	TEST_FIXTURE(ScalarTestValues, LessThanEqualTest)
	{
		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);

		CHECK(lhs <= lhs);
		CHECK((lhs <= rhs) == (scalar1 <= scalar2));
	}
	
	TEST_FIXTURE(ScalarTestValues, GreaterThanTest)
	{
		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);

		CHECK((lhs > rhs) == (scalar1 > scalar2));
	}

	TEST_FIXTURE(ScalarTestValues, GreaterThanEqualTest)
	{
		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);

		CHECK(lhs >= lhs);
		CHECK((lhs >= rhs) == (scalar1 >= scalar2));
	}

	//------------------------------------------------------------
	// Element access tests
	//------------------------------------------------------------

	TEST(ScalarSize)
	{
		CHECK(scalar_type::size() == 1);
	}

	//------------------------------------------------------------
	// Arithmetic operator tests
	//------------------------------------------------------------
	
	TEST_FIXTURE(ScalarTestValues, NegateOperator)
	{
		scalar_type test(scalar1);
		scalar_type negate = -test;

		CHECK_CLOSE(-scalar1, (value_type)negate, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, AddOperator)
	{
		const value_type expected = scalar1 + scalar2;

		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);

		// Check + operator
		scalar_type add = lhs + rhs;

		CHECK_CLOSE(expected, (value_type)add, tolerance);
		
		// Check += operator
		lhs += rhs;

		CHECK_CLOSE(expected, (value_type)lhs, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, SubOperator)
	{
		const value_type expected = scalar1 - scalar2;

		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);

		// Check - operator
		scalar_type sub = lhs - rhs;

		CHECK_CLOSE(expected, (value_type)sub, tolerance);
		
		// Check -= operator
		lhs -= rhs;

		CHECK_CLOSE(expected, (value_type)lhs, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, MulOperator)
	{
		const value_type expected = scalar1 * scalar2;

		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);

		// Check * operator
		scalar_type mul = lhs * rhs;

		CHECK_CLOSE(expected, (value_type)mul, tolerance);
		
		// Check *= operator
		lhs *= rhs;

		CHECK_CLOSE(expected, (value_type)lhs, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, MulOperatorVector4)
	{
		value_type expected[] =
		{
			scalar1 * values1[0],
			scalar1 * values1[1],
			scalar1 * values1[2],
			scalar1 * values1[3]
		} ;

		scalar_type lhs(scalar1);
		scalar_type::vector4_type rhs(values1);

		// Check / operator
		scalar_type::vector4_type mul = lhs * rhs;

		CHECK_VECTOR4_CLOSE(expected, mul, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, DivOperator)
	{
		const value_type expected = scalar1 / scalar2;

		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);

		// Check / operator
		scalar_type div = lhs / rhs;

		CHECK_CLOSE(expected, (value_type)div, tolerance);
		
		// Check /= operator
		lhs /= rhs;

		CHECK_CLOSE(expected, (value_type)lhs, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, DivOperatorVector4)
	{
		value_type expected[] =
		{
			scalar1 / values1[0],
			scalar1 / values1[1],
			scalar1 / values1[2],
			scalar1 / values1[3]
		} ;

		scalar_type lhs(scalar1);
		scalar_type::vector4_type rhs(values1);

		// Check / operator
		scalar_type::vector4_type div = lhs / rhs;

		CHECK_VECTOR4_CLOSE(expected, div, tolerance);
	}

	//------------------------------------------------------------
	// Arithmetic tests
	//------------------------------------------------------------

	TEST_FIXTURE(ScalarTestValues, InvValues)
	{
		const value_type expected = (value_type)1 / scalar1;

		scalar_type test(scalar1);
		scalar_type inv = scalar_type::inv(test);

		CHECK_CLOSE(expected, (value_type)inv, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, SqrtValues)
	{
		const value_type expected = std::sqrt(scalar1);

		scalar_type test(scalar1);
		scalar_type sqrt = scalar_type::sqrt(test);

		CHECK_CLOSE(expected, (value_type)sqrt, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, InvSqrtValues)
	{
		const value_type expected = (value_type)1 / std::sqrt(scalar1);

		scalar_type test(scalar1);
		scalar_type inv_sqrt = scalar_type::inv_sqrt(test);

		CHECK_CLOSE(expected, (value_type)inv_sqrt, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, MinValues)
	{
		const value_type expected = (scalar1 < scalar2) ? scalar1 : scalar2;

		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);
		scalar_type min = scalar_type::min(lhs, rhs);

		CHECK_CLOSE(expected, (value_type)min, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, MaxValues)
	{
		const value_type expected = (scalar1 > scalar2) ? scalar1 : scalar2;

		scalar_type lhs(scalar1);
		scalar_type rhs(scalar2);
		scalar_type max = scalar_type::max(lhs, rhs);

		CHECK_CLOSE(expected, (value_type)max, tolerance);
	}

	TEST_FIXTURE(ScalarTestValues, ClampValues)
	{
		const value_type min_value = (value_type)0;
		const value_type max_value = (value_type)10;
		const value_type mid_value = ((max_value - min_value) / (value_type)2) + min_value;

		scalar_type min(min_value);
		scalar_type max(max_value);

		scalar_type low (min_value - 1);
		scalar_type high(max_value + 1);
		scalar_type mid (mid_value);

		CHECK_CLOSE(min_value, (value_type)scalar_type::clamp(low , min, max), tolerance);
		CHECK_CLOSE(mid_value, (value_type)scalar_type::clamp(mid , min, max), tolerance);
		CHECK_CLOSE(max_value, (value_type)scalar_type::clamp(high, min, max), tolerance);
	}

} // end SUITE(ScalarInterface)
